/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

/**
 * General bootstrap of the application.
 */
angular.module('flowableModeler')
  .controller('EditorController', ['$rootScope', '$scope', '$http', '$q', '$routeParams', '$timeout', '$location', '$translate', '$modal', 'editorManager',
    function ($rootScope, $scope, $http, $q, $routeParams, $timeout, $location, $translate, $modal, editorManager) {

      $rootScope.editorFactory = $q.defer();

      $rootScope.forceSelectionRefresh = false;

      $rootScope.ignoreChanges = false; // by default never ignore changes

      $rootScope.validationErrors = [];

      $rootScope.staticIncludeVersion = Date.now();


      /**
       * Initialize the event bus: couple all Oryx events with a dispatch of the
       * event of the event bus. This way, it gets much easier to attach custom
       * logic to any event.
       */

      /* Helper method to fetch model from server (always needed) */

      function initScrollHandling() {
        var canvasSection = jQuery('#canvasSection');
        canvasSection.scroll(function () {

          // Hides the resizer and quick menu items during scrolling

          var selectedElements = editorManager.getSelection();
          var subSelectionElements = editorManager.getSubSelection();

          $scope.selectedElements = selectedElements;
          $scope.subSelectionElements = subSelectionElements;
          if (selectedElements && selectedElements.length > 0) {
            $rootScope.selectedElementBeforeScrolling = selectedElements[0];
          }

          jQuery('.Oryx_button').each(function (i, obj) {
            $scope.orginalOryxButtonStyle = obj.style.display;
            obj.style.display = 'none';
          });
          jQuery('.resizer_southeast').each(function (i, obj) {
            $scope.orginalResizerSEStyle = obj.style.display;
            obj.style.display = 'none';
          });
          jQuery('.resizer_northwest').each(function (i, obj) {
            $scope.orginalResizerNWStyle = obj.style.display;
            obj.style.display = 'none';
          });
          editorManager.handleEvents({type: ORYX.CONFIG.EVENT_CANVAS_SCROLL});
        });

        canvasSection.scrollStopped(function () {

          // Puts the quick menu items and resizer back when scroll is
          // stopped.

          editorManager.setSelection([]); // needed cause it checks for
                                          // element changes and does nothing
                                          // if the elements are the same
          editorManager.setSelection($scope.selectedElements, $scope.subSelectionElements);
          $scope.selectedElements = undefined;
          $scope.subSelectionElements = undefined;

          function handleDisplayProperty(obj) {
            if (jQuery(obj).position().top > 0) {
              obj.style.display = 'block';
            } else {
              obj.style.display = 'none';
            }
          }

          jQuery('.Oryx_button').each(function (i, obj) {
            handleDisplayProperty(obj);
          });
          jQuery('.resizer_southeast').each(function (i, obj) {
            handleDisplayProperty(obj);
          });
          jQuery('.resizer_northwest').each(function (i, obj) {
            handleDisplayProperty(obj);
          });

        });
      }

      /**
       * Initialize the Oryx Editor when the content has been loaded
       */
      if (!$rootScope.editorInitialized) {

        /**
         * A 'safer' apply that avoids concurrent updates (which $apply allows).
         */
        $rootScope.safeApply = function (fn) {
          if (this.$root) {
            var phase = this.$root.$$phase;
            if (phase == '$apply' || phase == '$digest') {
              if (fn && (typeof(fn) === 'function')) {
                fn();
              }
            } else {
              this.$apply(fn);
            }

          } else {
            this.$apply(fn);
          }
        };

        $rootScope.addHistoryItem = function (resourceId) {
          var modelMetaData = editorManager.getBaseModelData();

          var historyItem = {
            id: modelMetaData.modelId,
            name: modelMetaData.name,
            key: modelMetaData.key,
            stepId: resourceId,
            type: 'bpmnmodel'
          };

          if (editorManager.getCurrentModelId() != editorManager.getModelId()) {
            historyItem.subProcessId = editorManager.getCurrentModelId();
          }

          $rootScope.editorHistory.push(historyItem);
        };

        /**
         * Initialize the event bus: couple all Oryx events with a dispatch of
         * the event of the event bus. This way, it gets much easier to attach
         * custom logic to any event.
         */

        $rootScope.editorFactory.promise.then(function () {

          $rootScope.formItems = undefined;

          FLOWABLE.eventBus.editor = $rootScope.editor;

          var eventMappings = [
            {
              oryxType: ORYX.CONFIG.EVENT_SELECTION_CHANGED,
              flowableType: FLOWABLE.eventBus.EVENT_TYPE_SELECTION_CHANGE
            },
            {oryxType: ORYX.CONFIG.EVENT_DBLCLICK, flowableType: FLOWABLE.eventBus.EVENT_TYPE_DOUBLE_CLICK},
            {oryxType: ORYX.CONFIG.EVENT_MOUSEOUT, flowableType: FLOWABLE.eventBus.EVENT_TYPE_MOUSE_OUT},
            {oryxType: ORYX.CONFIG.EVENT_MOUSEOVER, flowableType: FLOWABLE.eventBus.EVENT_TYPE_MOUSE_OVER},
            {
              oryxType: ORYX.CONFIG.EVENT_EDITOR_INIT_COMPLETED,
              flowableType: FLOWABLE.eventBus.EVENT_TYPE_EDITOR_READY
            },
            {
              oryxType: ORYX.CONFIG.EVENT_PROPERTY_CHANGED,
              flowableType: FLOWABLE.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED
            }

          ];

          eventMappings.forEach(function (eventMapping) {
            editorManager.registerOnEvent(eventMapping.oryxType, function (event) {
              FLOWABLE.eventBus.dispatch(eventMapping.flowableType, event);
            });
          });

          // Show getting started if this is the first time (boolean true for
          // use local storage)
          // FLOWABLE_EDITOR_TOUR.gettingStarted($scope, $translate, $q,
          // true);
        });

        // Hook in resizing of main panels when window resizes
        // TODO: perhaps move to a separate JS-file?
        jQuery(window).resize(function () {

          // Calculate the offset based on the bottom of the module header
          var editorHeaderHeight = jQuery("#editor-header").height();
          var palette = jQuery('#paletteHelpWrapper');
          var propSection = jQuery('#propertiesHelpWrapper');
          var canvas = jQuery('#canvasSection');

          if (editorHeaderHeight == undefined || editorHeaderHeight === null
            || canvas === undefined || canvas === null) {
            return;
          }

          if ($rootScope.editor) {
            var selectedElements = editorManager.getSelection();
            var subSelectionElements = editorManager.getSelection();

            $scope.selectedElements = selectedElements;
            $scope.subSelectionElements = subSelectionElements;
            if (selectedElements && selectedElements.length > 0) {
              $rootScope.selectedElementBeforeScrolling = selectedElements[0];

              editorManager.setSelection([]); // needed cause it checks for
              // element changes and does
              // nothing if the elements are
              // the same
              editorManager.setSelection($scope.selectedElements, $scope.subSelectionElements);
              $scope.selectedElements = undefined;
              $scope.subSelectionElements = undefined;
            }
          }

          var totalAvailable = jQuery(window).height() - editorHeaderHeight;
          canvas.height(totalAvailable);
          propSection.height(totalAvailable);
          palette.height(totalAvailable);
          var paletteHeaderHeight = jQuery('#paletteHeader').height();
          var treeViewHeight = jQuery('#process-treeview-wrapper').height();
          jQuery('#paletteSection').height(totalAvailable - treeViewHeight - paletteHeaderHeight);
          var propertyHeaderHeight = jQuery('#propertyHeader').height();
          jQuery('.selected-item-body').height(totalAvailable - propertyHeaderHeight);

          // Update positions of the resize-markers, according to the canvas

          var actualCanvas = null;
          if (canvas && canvas[0].children[1]) {
            actualCanvas = canvas[0].children[1];
          }

          var canvasTop = canvas.position().top;
          var canvasLeft = canvas.position().left;
          var canvasHeight = canvas[0].clientHeight;
          var canvasWidth = canvas[0].clientWidth;
          var iconCenterOffset = 8;
          var widthDiff = 0;

          var actualWidth = 0;
          if (actualCanvas) {
            // In some browsers, the SVG-element clientwidth isn't
            // available, so we revert to the parent
            actualWidth = actualCanvas.clientWidth || actualCanvas.parentNode.clientWidth;
          }

          if (actualWidth < canvas[0].clientWidth) {
            widthDiff = actualWidth - canvas[0].clientWidth;
            // In case the canvas is smaller than the actual viewport, the
            // resizers should be moved
            canvasLeft -= widthDiff / 2;
            canvasWidth += widthDiff;
          }

          var iconWidth = 17;
          var iconOffset = 20;

          var north = jQuery('#canvas-grow-N');
          north.css('top', canvasTop + iconOffset + 'px');
          north.css('left', canvasLeft - 10 + (canvasWidth - iconWidth) / 2 + 'px');

          var south = jQuery('#canvas-grow-S');
          south.css('top', (canvasTop + canvasHeight - iconOffset - iconCenterOffset) + 'px');
          south.css('left', canvasLeft - 10 + (canvasWidth - iconWidth) / 2 + 'px');

          var east = jQuery('#canvas-grow-E');
          east.css('top', canvasTop - 10 + (canvasHeight - iconWidth) / 2 + 'px');
          east.css('left', (canvasLeft + canvasWidth - iconOffset - iconCenterOffset) + 'px');

          var west = jQuery('#canvas-grow-W');
          west.css('top', canvasTop - 10 + (canvasHeight - iconWidth) / 2 + 'px');
          west.css('left', canvasLeft + iconOffset + 'px');

          north = jQuery('#canvas-shrink-N');
          north.css('top', canvasTop + iconOffset + 'px');
          north.css('left', canvasLeft + 10 + (canvasWidth - iconWidth) / 2 + 'px');

          south = jQuery('#canvas-shrink-S');
          south.css('top', (canvasTop + canvasHeight - iconOffset - iconCenterOffset) + 'px');
          south.css('left', canvasLeft + 10 + (canvasWidth - iconWidth) / 2 + 'px');

          east = jQuery('#canvas-shrink-E');
          east.css('top', canvasTop + 10 + (canvasHeight - iconWidth) / 2 + 'px');
          east.css('left', (canvasLeft + canvasWidth - iconOffset - iconCenterOffset) + 'px');

          west = jQuery('#canvas-shrink-W');
          west.css('top', canvasTop + 10 + (canvasHeight - iconWidth) / 2 + 'px');
          west.css('left', canvasLeft + iconOffset + 'px');
        });

        jQuery(window).trigger('resize');

        jQuery.fn.scrollStopped = function (callback) {
          jQuery(this).scroll(function () {
            var self = this, $this = jQuery(self);
            if ($this.data('scrollTimeout')) {
              clearTimeout($this.data('scrollTimeout'));
            }
            $this.data('scrollTimeout', setTimeout(callback, 50, self));
          });
        };

        FLOWABLE.eventBus.addListener('ORYX-EDITOR-LOADED', function () {
          this.editorFactory.resolve();
          this.editorInitialized = true;
          this.modelData = editorManager.getBaseModelData();

        }, $rootScope);

        FLOWABLE.eventBus.addListener(FLOWABLE.eventBus.EVENT_TYPE_EDITOR_READY, function () {
          var url = window.location.href;
          var regex = new RegExp("[?&]subProcessId(=([^&#]*)|&|#|$)");
          var results = regex.exec(url);
          if (results && results[2]) {
            editorManager.edit(decodeURIComponent(results[2].replace(/\+/g, " ")));
          }
        });
      }

      $scope.$on('$locationChangeStart', function (event, next, current) {
        if ($rootScope.editor && !$rootScope.ignoreChanges) {
          var plugins = $rootScope.editor.loadedPlugins;

          var savePlugin;
          for (var i = 0; i < plugins.length; i++) {
            if (plugins[i].type == 'ORYX.Plugins.Save') {
              savePlugin = plugins[i];
              break;
            }
          }

          if (savePlugin && savePlugin.hasChanges()) {
            // Always prevent location from changing. We'll use a popup to
            // determine the action we want to take
            event.preventDefault();

            if (!$scope.unsavedChangesModalInstance) {

              var handleResponseFunction = function (discard) {
                $scope.unsavedChangesModalInstance = undefined;
                if (discard) {
                  $rootScope.ignoreChanges = true;
                  $location.url(next.substring(next.indexOf('/#') + 2));
                } else {
                  $rootScope.ignoreChanges = false;
                  $rootScope.setMainPageById('processes');
                }
              };

              $scope.handleResponseFunction = handleResponseFunction;

              _internalCreateModal({
                template: 'views/popups/unsaved-changes.html',
                scope: $scope
              }, $modal, $scope);
            }
          }
        }
      });

      // Always needed, cause the DOM element on wich the scroll event listeners
      // are attached are changed for every new model
      initScrollHandling();

      var modelId = $routeParams.modelId;
      editorManager.setModelId(modelId);
      editorManager.setToken($routeParams.token)
      // we first initialize the stencilset used by the editor. The editorId is
      // always the modelId.
      $http.get(FLOWABLE.URL.getStencilSet()).then(function (response) {
        var baseUrl = "http://b3mn.org/stencilset/";
        editorManager.setStencilData(response.data);
        // the stencilset alters the data ref!
        var stencilSet = new ORYX.Core.StencilSet.StencilSet(baseUrl, response.data);
        ORYX.Core.StencilSet.loadStencilSet(baseUrl, stencilSet, modelId);
        // after the stencilset is loaded we make sure the plugins.xml is loaded.
        return $http.get(ORYX.CONFIG.PLUGINS_CONFIG);
      }).then(function (response) {
        ORYX._loadPlugins(response.data);
        return $http({
          method: 'GET',
          ignoreErrors: true,
          headers: {
            'Token': editorManager.getToken()
          },
          url: FLOWABLE.URL.getModel(modelId)
        });
      }).then(function (response) {
        editorManager.bootEditor(response);
      }).catch(function (error) {
        console.log(error);
      });

      // minihack to make sure mousebind events are processed if the modeler is used
      // in an iframe.
      // selecting an element and pressing "del" could sometimes not trigger an
      // event.
      jQuery(window).focus();

    }]);

angular.module('flowableModeler')
  .controller('EditorUnsavedChangesPopupCtrl', ['$rootScope', '$scope', '$http', '$location', '$window', function ($rootScope, $scope, $http, $location, $window) {

    $scope.discard = function () {
      if ($scope.handleResponseFunction) {
        $scope.handleResponseFunction(true);
        // Also clear any 'onbeforeunload', added by oryx
        $window.onbeforeunload = undefined;
      }
      $scope.$hide();
    };

    $scope.save = function () {
      if ($scope.handleResponseFunction) {
        $scope.handleResponseFunction(false);
      }
      $scope.$hide();
    };

    $scope.cancel = function () {
      if ($scope.handleResponseFunction) {
        $scope.handleResponseFunction(null);
      }
      $scope.$hide();
    };
  }]);

